//
//  PSActiveRecord.m
//  PSExtensions
//
//  Created by PoiSon on 15/9/28.
//  Copyright © 2015年 yerl. All rights reserved.
//

#import "PSDicRecord_private.h"

@implementation PSDicRecord{
    NSMutableArray *_registedModel;
}

- (instancetype)init{
    PSAssert(NO, @"use initWithDatasource:forConfig: instead");
    return self = [self initWithDatasource:@"" forConfig:@""];
}

- (instancetype)initWithDatasource:(NSString *)dataSource{
    return [self initWithDatasource:dataSource forConfig:@"main"];
}

- (nonnull instancetype)initWithDatasource:(nonnull NSString*)datasource forConfig:(nonnull NSString *)configName{
    PSAssert(datasource.length, @"datasource can not be nil or empty");
    PSAssert(configName.length, @"configName can not be nil or empty");
    if (self = [super init]) {
        _registedModel = [NSMutableArray new];
        
        _datasource = datasource;
        _configName = configName;
    }
    return self;
}

- (NSString *)description{
    return format(@"<PSDicRecord %p>:\n{\n   Models : %@\n   Datasource : %@\n}", [super description], [_registedModel ps_join:@","], self.datasource);
}

- (NSString *)debugDescription{
    return format(@"<PSDicRecord %p>:\n{\n   Models : %@\n   Datasource : %@\n}", [super description], [_registedModel ps_join:@","], self.datasource);
}

- (id<PSContainerFactory>)containerFactory{
    return _containerFactory?: [PSCaseSensitiveContainerFactory new];
}
 
- (void)registerModel:(Class)model{
    PSAssertParameter([model isSubclassOfClass:PSModel.class]);
    [_registedModel addObject:model];
}

- (void)registerConvertor:(id<PSTypeConvertor>)convertor{
    PSAssertParameter([convertor conformsToProtocol:@protocol(PSTypeConvertor)]);
    [PSDbKit addConvertor:convertor];
}

- (void)start{
    PSAssert(self.datasource, @"you must set up a database");
    PSAssert(_registedModel.count, @"you must registe a model at lease");
    
    //Auto create path
    [[[PSFile fileWithPath:self.datasource] parent] createIfNotExists];
    
    PSDbConfig *config = [[PSDbConfig alloc] initWithName:_configName datasource:_datasource showSql:_showSql];
    config.containerFactory = self.containerFactory;
    
    [PSDbKit addModel:[PSTable class] toConfigMapping:config];
    [PSDbKit addModel:[PSColumn class] toConfigMapping:config];
    [PSDbKit addConfig:config];
    
    NSDictionary<NSString *, PSTable *> *tableConfig = [self buildTableConfig:config];
    BOOL result = [[PSDb use:config.name] tx:^BOOL(PSDb * db){
        for (Class model in _registedModel) {
            NSString *tableName = [model tableName];
            PSTable *tableStruct = [tableConfig objectForKey:tableName];
            
            if (!tableStruct) {
                //create table
                tableStruct = [PSTable buildTableWithModel:model];
                PSAssert([db updateWithSql:[PSSqlBuilder forCreate:tableStruct]], @"execute creation failed");
                PSAssert([db batchSqls:[model migrateForm:0 to:tableStruct.version] error:nil], @"execute migration failed");
                PSAssert([tableStruct save], @"save table info for %@ failed", NSStringFromClass(model));
            }else{
                //update table
                PSAssert(tableStruct.type == model, @"init PSDicRecord fail cause conflict between %@ and %@", NSStringFromClass(tableStruct.type), NSStringFromClass(model));
                PSTable *currentStruct = [PSTable buildTableWithModel:model];
                
                if (tableStruct.version < currentStruct.version) {
                    NSArray<PSRecord *> *record = [db find:format(@"pragma table_info ('%@')", currentStruct.name)];
                    NSArray<NSString *> *existedColumns = [[record.ps_stream select:^id _Nonnull(id  _Nonnull e) {
                        return [[e valueForKey:@"name"] lowercaseString];
                    }] array];
                    
                    //find new columns
                    NSArray<PSColumn *> *newColumns = [[currentStruct.cols.ps_stream where:^BOOL(id  _Nonnull e) {
                        return ![tableStruct.cols containsObject:e] && ![existedColumns containsObject:[[e name] lowercaseString]];
                    }] array];
                    //build add column sqls
                    NSArray<PSSql *> *addSqls = [[newColumns.ps_stream select:^id _Nonnull(id  _Nonnull e) {
                        return [PSSqlBuilder forAddColumn:e toTable:currentStruct];
                    }] array];
                    PSAssert([db batchSqls:addSqls error:nil], @"add columns failed");
                    
                    //execute migration
                    PSAssert([db batchSqls:[model migrateForm:tableStruct.version to:currentStruct.version] error:nil], @"execute migration failed");
                    
                    //add new column structure
                    currentStruct.ID = tableStruct.ID;
                    PSAssert([currentStruct updateAll], @"update table structure failed");
                    tableStruct = currentStruct;
                }
            }
            [PSDbKit addModel:model toConfigMapping:config];
            [PSDbKit addModel:model toTableMapping:tableStruct];
        }
        return YES;
    } error: nil];
    PSAssert(result, @"Initialize failed.");
}

- (NSDictionary<NSString *, PSTable *> *)buildTableConfig:(PSDbConfig *)config{
    // initialize
    PSDb *db = [PSDb use:config.name];
    
    PSTable *configTable = [PSTable buildTableWithModel:PSTable.class];
    [PSDbKit addModel:PSTable.class toTableMapping:configTable];
    PSAssert([db updateWithSql:[PSSqlBuilder forCreate:configTable]], @"unable to create config table");
    
    NSDictionary *result = [[[PSTable dao] findAll].ps_stream dictionary:^id _Nonnull(id  _Nonnull e) {
        return PSArrayTuple([e name], e);
    }];
    return result;
}
@end